/**
 * @file crud
 * @author liaoxuezhi@baidu.com
 */
/* eslint fecs-indent: [0, "space", 2, 2] */
import * as findLast from 'lodash/findLast';
import * as React from 'react';
import {
  PureComponent,
  PropTypes
} from 'react';
import * as cx from 'classnames';
import {Renderer, render, renderChildren, buildApi, buildLink,  filter, normalizeLink, evalExpression} from '../renderer';
import reducer from './reducer';
import * as assign from 'object-assign';
import * as actions from './actions';
import {push, goBack} from 'react-router-redux';
import {confirm, alert} from '../../widgets/alert/alert';
import * as CustomPropTypes from '../propTypes';
import Scoped from '../hoc/scoped';
import './page.scss';
import * as schema from './page-schema.json';
import TooltipWrapper from '../../widgets/tooltip';
import {sendNotify} from '../../actions/asist';
import onExpression from '../hoc/on-expression';
import * as copy from 'copy-to-clipboard';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';

export class Page extends PureComponent {
  static propTypes = {
    name: PropTypes.string,
    title: PropTypes.string,
    className: PropTypes.string,
    locals: PropTypes.object,
    sendNotify: PropTypes.func.isRequired,
    fetchPage: PropTypes.func.isRequired,
    doAction: PropTypes.func.isRequired,
    state: PropTypes.object.isRequired,
    subTitle: PropTypes.string,
    remark: PropTypes.string,
    toolbar: CustomPropTypes.container,
    aside: CustomPropTypes.container,
    body: CustomPropTypes.container,
    initFetch: PropTypes.bool
  };

  static defaultProps = {
    locals: {},
    toolbarClassName: 'v-middle padder-md text-right bg-light b-b wrapper-sm clear',
    bodyClassName: 'wrapper',
    asideClassName: 'w-md bg-light dk b-r bg-auto',
    headerClassName: 'bg-light b-b wrapper',
    messages: {},
    primaryField: 'id',
    initFetch: true,
    copy
  };

  static contextTypes = {
    scoped: PropTypes.object.isRequired,
    store: PropTypes.object
  };

  constructor(props) {
    super(props);
    const location = props.location;

    this.state = {
      action: null,
      dialog: null,
      ctxData: null,
      data: assign({}, props.state.data, props.data, location.query, {
        query: location.query
      })
    };

    this.handleAction = this.handleAction.bind(this);
    this.handleDialogResolved = this.handleDialogResolved.bind(this);
    this.close = this.close.bind(this);
    this.receive = this.receive.bind(this);
    this.reload = this.reload.bind(this);
    this.handleClick = this.handleClick.bind(this);
  }

  componentWillMount() {
    const {
      pageId,
      primaryField
    } = this.props;
    const scoped = this.context.scoped;
    const data = this.state.data;

    pageId && scoped.setData('pageId', pageId);
    this.context.scoped.registerComponent(this);
  }

  componentDidMount() {
    this.props.initFetch && this.reload();
  }

  componentWillReceiveProps(nextProps) {
    const props = this.props;
    const state = nextProps.state;
    const location = nextProps.location;
    const primaryField = nextProps.primaryField;

    // 注意， assign 的顺序不一样。
    if (props.state.data !== state.data) {
      this.setState({
        data: assign({}, this.state.data, state.data)
      });
    } else if (props.data !== nextProps.data) {
      this.setState({
        data: assign({}, state.data, nextProps.data, location.query, {
          query: location.query
        })
      });
    }

    if (props.location !== location) {
      const id = props.location.query[primaryField];
      const nextId = location.query[primaryField];

      this.setState({
        data: assign({}, this.state.data, location.query, {
          query: location.query
        })
      }, () => {
        if (id !== nextId && nextProps.initApi) {
          nextProps.fetchPage(nextProps.scopedKey, nextProps.initApi, this.state.data, nextProps.locals);
        }
      });
    }

    // action 提示
    // if (props.state.action.saving && !nextProps.state.action.saving) {
    //   const actionState = state.action;
    //
    //   if (actionState.invalid) {
    //     sendNotify({
    //       message: action.messages && action.messages.failed || (actionState.error instanceof Error ? actionState.error.message : actionState.message) || '操作失败',
    //       level: 'error'
    //     });
    //   } else {
    //     sendNotify({
    //       message: action.messages && action.messages.success || actionState.message || '操作成功',
    //       level: 'success'
    //     });
    //
    //     action.reload && this.context.scoped.reload(action.reload);
    //     action.feedback && this.handleFeedback(action.feedback, this.state.data);
    //   }
    // }
  }

  componentWillUnmount() {
    this.context.scoped.unRegisterComponent(this);
  }

  // alias for handleAction
  doAction(action, data = {}) {
    this.handleAction(action, data);
  }

  handleAction(action, data = {}) {
    data = assign({}, data);
    const locals = assign({}, this.props.locals, this.state.data);
    const sendNotify = this.props.sendNotify;

    // 兼顾老用法
    if (action.actionType === 'open') {
      action.actionType = 'url';
      action.blank = true;
      action.url = action.url || action.href || action.action;
    }

    if (action.actionType === 'dialog') {
      return this.setState({
        action,
        dialog: action.dialog,
        ctxData: data
      });
    } else if (action.actionType === 'ajax') {
      const {
        scopedKey,
        doAction
      } = this.props;

      let fn = () => this.setState({action: action}, () => {
        doAction(scopedKey, action.api || action.action, data, locals)
          .then(_action => {
            const {
              messages,
              sendNotify
            } = this.props;

            if (_action.error) {
              const error = _action.payload;
              let msg = messages.saveFailed || action.messages && action.messages.failed || (error instanceof Error ? error.message : error) || '操作失败';
              sendNotify({
                message: msg,
                level: 'error'
              });
            } else {
              sendNotify({
                message: messages.saveSuccess || action.messages && action.messages.success || _action.payload.msg || '操作成功',
                level: 'success'
              });

              action.reload && this.context.scoped.reload(action.reload);
              action.feedback && this.handleFeedback(action.feedback, this.state.data);
            }
          });
      });
      return action.confirmText ? confirm(filter(action.confirmText, data)).then(fn) : fn();
    } else if (action.actionType === 'link') {
      const push = this.props.push;
      const goBack = this.props.goBack;
      action.link = action.link || action.action; // 适配老用法
      let fn = () => action.link === 'goBack' ? goBack() : push(normalizeLink(filter(action.link || action.action, data)));
      return action.confirmText ? confirm(filter(action.confirmText, data)).then(fn) : fn();
    } else if (action.actionType === 'url') {
      let fn = action.blank === false ? () => location.href = buildLink(action.url || action.action, data) : () => window.open(buildLink(action.url || action.action, data));
      return action.confirmText ? confirm(filter(action.confirmText, data)).then(fn) : fn();
    } else if (action.actionType === 'reload') {
      const scoped = this.context.scoped;
      scoped.reload(action.reload || action.target);
      return;
    } else if (action.actionType === 'copy') {
      const copy = this.props.copy;
      const text = filter(action.content, data);
      if (copy(text) && sendNotify) {
        sendNotify({
          level: 'success',
          title: '拷贝成功',
          message: '内容已拷贝到剪切板'
        });
      }
      return;
    } else if (action.target) {
      const scoped = this.context.scoped;
      const target = scoped.getComponentByName(action.target);
      target && target.doAction && target.doAction(action);
      return;
    }

    alert(`Page 层，无法不能识别的 action ${JSON.stringify(action, null, 2)}: ${JSON.stringify(data, null, 2)}`);
  }

  handleFeedback(feedback, data) {
    this.handleAction({
      actionType: 'dialog',
      dialog: feedback
    }, data);
  }

  handleDialogResolved(values) {
    const scoped = this.context.scoped;
    const components = scoped.getComponents();
    const crud = findLast(components, (component => component.props.type === 'crud'));

    this.close(() => {
      const action = this.state.action;
      let reload = action.reload;
      reload ? scoped.reload(reload) : crud && crud.search();
    });
  }

  close(fn) {
    this.setState({
      dialog: null,
      currentRow: null
    }, typeof fn === 'function' ? fn : null);
  }

  receive(data) {
    this.setState({
      data: assign({}, this.state.data, data)
    }, this.reload);
  }

  reload() {
    const {
      scopedKey,
      initApi,
      fetchPage,
      locals
    } = this.props;

    if (initApi) {
      fetchPage(scopedKey, initApi, this.state.data, locals)
        .then(action => {
          const {
            messages,
            sendNotify
          } = this.props;

          if (action.error) {
            const error = action.payload;
            let msg = messages.fetchFailed || (error instanceof Error ? error.message : error) || '获取数据失败';
            sendNotify({
              message: msg,
              level: 'error'
            });
          } else if (messages.fetchSuccess) {
            sendNotify({
              message: messages.fetchSuccess,
              level: 'success'
            });
          }
        });
    }
  }

  handleClick(e) {
    const link = e.target.getAttribute('data-link');

    if (link) {
      const push = this.props.push;
      e.preventDefault();
      push(normalizeLink(link));
    }
  }

  renderContainer(children, config = null, suffix = '', key) {
    const {
      $path,
      sendNotify,
      location,
      state,
      $key
    } = this.props;

    if (!children) {
      return null;
    }

    const locals = assign({}, this.props.locals, {
      $state: state
    });

    return renderChildren(`${$path}${suffix ? `/${suffix}` : ''}`, children, {
      sendNotify,
      locals,
      data: this.state.data,
      location,
      loading: state.fetching || state.action.saving,
      onRequestAction: this.handleAction
    }, config ? subSchema => assign({}, config, subSchema): null, null, `${$key}.${key}`);
  }

  renderHeader() {
    const {
      title,
      subTitle,
      remark,
      headerClassName,
      toolbarClassName,
      toolbar,
      locals,
      $path
    } = this.props;

    const data = this.state.data;
    let header, right;

    if (title || subTitle) {
      header = (
        <div className={cx(headerClassName)}>
          {title && (
            <h1 className="m-n font-thin h3 text-black">
              {filter(title, data, locals)}
              {remark && (
                <TooltipWrapper id={$path} tooltip={filter(remark, data, locals)}>
                  <a
                    className="fa fa-question-circle m-l-sm text-base v-middle text-muted"
                  />
                </TooltipWrapper>
              )}
            </h1>
          )}
          {subTitle && (<small className="text-muted">{filter(subTitle, data, locals)}</small>)}
        </div>
      );
    }

    if (toolbar) {
      right = (
        <div className={toolbarClassName}>
          <div className="page-toolbar">
            {this.renderContainer(toolbar, null, 'toolbar', 'toolbar')}
          </div>
        </div>
      );
    }


    if (header && right) {
      return (
        <div className="hbox hbox-auto-xs h-auto">
          {React.cloneElement(header, {
            className: cx('col', header.props.className)
          })}
          {React.cloneElement(right, {
            className: cx('col', right.props.className)
          })}
        </div>
      );
    }

    return header || right;
  }

  render() {
    const {
      className,
      sendNotify,
      aside,
      asideClassName,
      bodyClassName,
      form,
      crud,
      $path,
      state
    } = this.props;
    let body = this.props.body;

    const {dialog, ctxData, data} = this.state;
    const locals = assign({}, this.props.locals, data);

    // 兼容老用法，不推荐用户使用。
    if (!body) {
      crud && (body = assign({
        type: 'crud'
      }, crud));

      form && (body = assign({
        type: 'form'
      }, form));
    }

    let main = (
      <div>
        {this.renderHeader()}
        {state.fetching ? (
          <div className="page-spinner">
            <i className="fa fa-spinner fa-spin fa-3x fa-fw text-white" />
          </div>
        ) : null}
        {body ? (
          <div className={cx('page-body', bodyClassName)}>{this.renderContainer(body, null, '', 'body')}</div>
        ) : null}
      </div>
    );

    if (aside) {
      main = (
        <div className="hbox hbox-auto-xs">
          <div className={cx('col', asideClassName)}>{this.renderContainer(aside, {
            className: 'wrapper-md'
          }, 'aside', 'aside')}</div>
          {React.cloneElement(main, {
            className: 'col'
          })}
        </div>
      )
    }

    return (
      <div className={cx("page-wrapper", className)} onClick={this.handleClick}>
        {main}

        {dialog ? render(`${$path}/dialog`, dialog, {
          data: ctxData,
          locals,
          sendNotify,
          onClose: this.close,
          onResolved: this.handleDialogResolved
        }) : null}
      </div>
    );
  }
}

@Renderer({
  name: 'page',
  desc: '...',
  test: /^page$/,
  reducer,
  schema
}, null, assign({
  push,
  goBack,
  sendNotify
}, actions))
@onExpression
@Scoped
export default class PageRenderer extends Page {}
